#!/usr/bin/env bash

include "arguments.sh"
include "test.sh"

_OBJECT_struct_name=""
_OBJECT_cons_eval=""
_OBJECT_ctor_eval=""
_OBJECT_dtor_eval=""
_OBJECT_dest_eval=""

function object::_option::cargs()
{
    local ctor_id="1"
    local valsname="$(arguments::get_val)"
    for valname in $(sed "s@,@ @g" <<< "$valsname"); do
        _OBJECT_ctor_eval+="
            eval \"__${_OBJECT_struct_name}_\${!id}_${valname}=\$$((ctor_id++))\""
    done
}

function object::_option::string()
{
    local valname="$(arguments::get_val)"
    _OBJECT_cons_eval+="
        function ${_OBJECT_struct_name}::${valname}()
        {
            local name=\"__${_OBJECT_struct_name}_\${!1}_${valname}\"
            if [ \$# == 1 ]; then
                echo \${!name}
            elif [ \$# == 2 ]; then
                eval \"\${name}=\$2\"
            else
                assert_fail \"无法处理参数: \$*\"
            fi
        }
    "
    _OBJECT_dtor_eval+="
            eval \"unset __${_OBJECT_struct_name}_\${!id}_${valname}\""
    _OBJECT_dest_eval+="
            unset -f ${_OBJECT_struct_name}::${valname}"
}

function object::_option::array()
{
    local valname="$(arguments::get_val)"
    _OBJECT_cons_eval+="
        function ${_OBJECT_struct_name}::${valname}()
        {
            local name=\"__${_OBJECT_struct_name}_\${!1}_${valname}\"
            if [ \$# == 2 ]; then
                if [ \$2 == len ]; then
                    eval \"echo \\\${#\$name[*]}\"
                else
                    eval \"echo \\\${\$name[\$2]}\"
                fi
            elif [ \$# == 3 ]; then
                if [ \$2 == push ]; then
                    eval \"\$name+=(\\\"\$3\\\")\"
                elif [ \$2 == erase ]; then
                    local object__option_array_swap=()
                    local object__option_array_len=0
                    eval \"object__option_array_len=\\\${#\$name[*]}\"
                    for (( object__option_array_i = 0; object__option_array_i < \$object__option_array_len; object__option_array_i++ )); do
                        local object__option_array_value
                        eval \"object__option_array_value=\\\"\\\${\$name[\\\$object__option_array_i]}\\\"\"
                        if [ \$object__option_array_value == \$3 ]; then
                            continue
                        fi
                        object__option_array_swap+=(\"\$object__option_array_value\")
                    done
                    eval \"\$name=(\${object__option_array_swap[*]})\"
                else
                    assert_fail \"没有该操作: \$*\"
                fi
            else
                assert_fail \"无法处理参数: \$*\"
            fi
        }
    "
    _OBJECT_ctor_eval+="
            eval \"__${_OBJECT_struct_name}_\${!id}_${valname}=()\""
    _OBJECT_dtor_eval+="
            eval \"unset __${_OBJECT_struct_name}_\${!id}_${valname}\""
    _OBJECT_dest_eval+="
            unset -f ${_OBJECT_struct_name}::${valname}"
}

function object::define_struct()
{
    _OBJECT_struct_name="$1"
    shift

    arguments $*
    while ! arguments::is_end; do
        call "object::_option::$(arguments::get_key)"
        arguments::next
    done
    arguments::destroy

    eval "
        _${_OBJECT_struct_name}_nextid=0
        _${_OBJECT_struct_name}_refs=0
        function ${_OBJECT_struct_name}()
        {
            local symbol=\$1; shift
            local id=_${_OBJECT_struct_name}_nextid $_OBJECT_ctor_eval
            (( \$symbol = _${_OBJECT_struct_name}_nextid++ ))
            (( _${_OBJECT_struct_name}_refs++ ))
        }

        function ${_OBJECT_struct_name}::destroy()
        {
            local id=\${!1} $_OBJECT_dtor_eval
            (( _${_OBJECT_struct_name}_refs-- ))
            if (( _${_OBJECT_struct_name}_refs != 0 )); then
                return 0
            fi
            $_OBJECT_dest_eval
        }
        $_OBJECT_cons_eval
    "
    unset _OBJECT_struct_name
    unset _OBJECT_cons_eval
    unset _OBJECT_ctor_eval
    unset _OBJECT_dtor_eval
    unset _OBJECT_dest_eval
}

function object::test1()
{
    object::define_struct OBJ "
        --cargs=url,name
        --string=url
        --string=name
        --array=actions
    "

    OBJ obj1 www.baidu.com baidu

    # cargs
    test::run_cmd OBJ::url obj1
    test::should_be www.baidu.com
    test::run_cmd OBJ::name obj1
    test::should_be baidu

    # string setter
    OBJ::url obj1 fanyi.youdao.com
    test::run_cmd OBJ::url obj1
    test::should_be fanyi.youdao.com

    # array operation
    test::run_cmd OBJ::actions obj1 len
    test::should_be 0

    ## push 123
    OBJ::actions obj1 push 123
    test::run_cmd OBJ::actions obj1 len
    test::should_be 1
    test::run_cmd OBJ::actions obj1 0
    test::should_be 123

    # other object
    OBJ obj2 www.baidu.com baidu
    test::run_cmd OBJ::name obj1
    test::should_be baidu
    test::run_cmd OBJ::url obj1
    test::should_be fanyi.youdao.com

    # other object destroy
    OBJ::destroy obj2
    test::run_cmd OBJ::name obj1
    test::should_be baidu
    test::run_cmd OBJ::url obj1
    test::should_be fanyi.youdao.com
    OBJ::actions obj1 push 456
    OBJ::actions obj1 push 789
    test::run_cmd OBJ::actions obj1 len
    test::should_be 3
    test::run_cmd OBJ::actions obj1 0
    test::should_be 123
    test::run_cmd OBJ::actions obj1 1
    test::should_be 456
    test::run_cmd OBJ::actions obj1 2
    test::should_be 789
}
test::register_test object::test1

function object::test2()
{
    object::define_struct OBJ "
        --array=actions
    "

    OBJ obj www.baidu.com baidu
    OBJ::actions obj push 123
    OBJ::actions obj push 456
    OBJ::actions obj push 789
    test::run_cmd OBJ::actions obj len
    test::should_be 3
    test::run_cmd OBJ::actions obj 1
    test::should_be 456

    # erase
    OBJ::actions obj erase 456
    test::run_cmd OBJ::actions obj len
    test::should_be 2
    test::run_cmd OBJ::actions obj 1
    test::should_be 789

    OBJ::destroy obj
}
test::register_test object::test2
